package com.codeshellme.bookmanager.config;

import com.codeshellme.bookmanager.security.JwtTokenUtil;
import com.codeshellme.bookmanager.security.JwtAuthenticationTokenFilter;
import com.codeshellme.bookmanager.service.UmsMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * SpringSecurity 本质上是一个过滤器链：有很多的过滤器
 * 比如 UsernamePasswordAuthenticationFilter：对/login 的 POST 请求做拦截，校验表单中用户名，密码
 * <p>
 * 两个重要的接口：
 * UserDetailsService：当需要自定义逻辑时（查询数据库用户名，密码），需要实现 UserDetailsService 接口
 * PasswordEncoder：密码加密
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private UmsMenuService umsMenuService;

    @Override   // 设置权限
    protected void configure(HttpSecurity httpSecurity) throws Exception {

        // 设置退出操作
        httpSecurity.logout()
                .logoutUrl("/logout")                   // 设置退出路径
                .logoutSuccessUrl("/")                  // 设置退出后访问的路径
                .permitAll();

        // 设置没有权限时，自动跳转到的页面
        httpSecurity.exceptionHandling().accessDeniedPage("/unauth");

        // 从数据库中加载菜单应有的角色权限
        List<Map<String, Object>> roles = umsMenuService.queryMenuUrlNeedRoleIds();
        for (Map<String, Object> role: roles) {
            String menuUrl = role.get("menu_url").toString();
            String roleIds = role.get("role_ids").toString();

            // 为菜单url 设置角色
            httpSecurity.authorizeRequests().antMatchers(menuUrl).hasAnyRole(roleIds);
        }

        httpSecurity
                .formLogin()                        // 自定义登录页面设置
                .loginPage("/login.html")           // 设置登录页面
                .loginProcessingUrl("/login")       // 登录页面的访问路径
                .usernameParameter("username")      // input 框 name
                .passwordParameter("password")      // input 框 name
                .successHandler(                    // 登录成功处理器
                        new UmsUserAuthSuccHandler("/main"))
                .failureHandler(                    // 登录失败处理器
                        new UmsUserAuthFailHandler("/"))
//                    .defaultSuccessUrl("/main")    // 登录成功后的默认跳转路径
                .permitAll()
                .and()
                .authorizeRequests()                    // 授权设置 TODO main 是要去掉的
                .antMatchers("/login", "/", "/index").permitAll()   //不需要登录就可以被访问的资源路径
                .anyRequest().authenticated()
                .and()
                .csrf().disable();                      // 关闭 csrf 防护
//                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);    // 禁用 SESSION
//                .addFilterBefore(jwtAuthenticationTokenFilter(),        // 整合 jwt
//                        UsernamePasswordAuthenticationFilter.class);
    }

    // 开放静态资源
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers(
                "/css/**",
                "/error/**",
                "/images/**",
                "/jquery-easyui/**",
                "/js/**",
                "/wu/**",
                // TODO 以后都要去掉
//                "/menu",
//                "/queryMenus",
//                "/queryMenuById",
//                "/queryAllDirMenus",
//                "/updateMenu",
//                "/addMenu",
//                "/deleteMenuById",
//                "/queryRoleMenusByUserId",
//                "/role",
//                "/queryAllRoles",
//                "/queryMenuTreeByRoleId",
//                "/processRoleMenus",
//                "/addRole",
//                "/deleteRoleById",
//                "/updateRole",
//                "/verifyRoleSign",
//                "/user",
//                "/queryAllUsers",
//                "/updateUser",
//                "/addUser",
//                "/deleteUserById",
//                "/verifyUserName",
//                "/sys_dict",
//                "/querySysDict",
//                "/sys_author",
//                "/queryAllAuthors",
//                "/queryAllNations",
//                "/updateAuthor",
//                "/addAuthor",
//                "/deleteAuthorById",
//                "/sys_book",
//                "/queryAllSysBooks",
                "/queryBookTypes",
                "/queryBookAuthors",
//                "/updateBookItem",
//                "/addBookItem",
//                "/deleteBookItemById",
//                "/verifyBookItemName",
//                "/book_stock",
                "/queryAllBookStocks"
//                "/verifyBookStockId",
//                "/addBookStock",
//                "/updateBookStockNum",
//                "/deleteBookStockById",
//                "/book_borrow",
//                "/queryAllBookBorrows",
//                "/bookReturn",
//                "/bookBorrowSave"
        );
    }


    @Override   // 验证用户名密码
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(daoAuthenticationProvider());
    }


    @Bean   // 用于密码加密
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider dao = new DaoAuthenticationProvider();
        dao.setHideUserNotFoundExceptions(false);
        dao.setUserDetailsService(userDetailsService);
        dao.setPasswordEncoder(passwordEncoder());
        return dao;
    }

    /*
    @Bean
    public JwtTokenUtil jwtTokenUtil() {
        return new JwtTokenUtil();
    }

    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
        return new JwtAuthenticationTokenFilter();
    }
    */


    // 自定义认证失败处理器
    class UmsUserAuthFailHandler implements AuthenticationFailureHandler {

        private String url;


        /**
         * @param url 登录失败后，要跳转到的路径
         */
        UmsUserAuthFailHandler(String url) {
            this.url = url;
        }

        @Override
        public void onAuthenticationFailure(HttpServletRequest request,
                                            HttpServletResponse response,
                                            AuthenticationException e) throws IOException {

            String msg = e.getMessage();
            if (msg.equals("Bad credentials")) {
                msg = "密码错误";
            }

            request.getSession().setAttribute("msg", msg);
            response.sendRedirect(url);
        }
    }

    // 自定义认证成功处理器
    class UmsUserAuthSuccHandler implements AuthenticationSuccessHandler {

        private String url;


        /**
         * @param url 登录成功后，要跳转到的路径
         */
        UmsUserAuthSuccHandler(String url) {
            this.url = url;
        }

        @Override
        public void onAuthenticationSuccess(HttpServletRequest request,
                                            HttpServletResponse response,
                                            Authentication auth) throws IOException {

            request.getSession().setAttribute("username", auth.getName());
            response.sendRedirect(url);
        }
    }

}
